#!/usr/bin/perl -w

#  Imdb perl module to conduct searches on the imdb website.
#  This code is based on 'imdb.pl' originated from the 
#  Mythtv project revision 18566 (Version 0.21).
#     http://svn.mythtv.org/trac/browser/branches/release-0-21-fixes/mythplugins/mythvideo/mythvideo/scripts/imdb.pl
#
#  Thanks to the origianl authors;
#     Tim Harvey (tharvey AT alumni.calpoly DOT edu)
#     Andrei Rjeousski
#
#  Heavily modified to fit into the grabber plugin architecture of
#  mythvideo-scanner.

package grabbers::imdb;

use LWP::Simple;      # libwww-perl providing simple HTML get actions
use HTML::Entities;
use URI::Escape;
use Text::Levenshtein qw(distance);

use vars qw($opt_h $opt_r $opt_d $opt_i $opt_v $opt_D $opt_M $opt_P);
use Getopt::Std;

my $_rev = '$Rev$';

my @countries = qw(USA UK Canada Japan);

# Exports
our $VERSION = "2.0";

sub title
{
	return "Imdb";
}
sub desc
{
	return "Search on IMDB";
}

sub version
{
	$_rev =~ s/\$//g;
	return $VERSION.", ".$_rev ;
}

sub search 
{
	my $self = shift;
	my $title = shift;
	my $subtitle = shift;
	my $season = shift;
	my $episode = shift;
	my $year = shift;
	my %details;	

	$ref = searchImdb($title.": ".$subtitle,"", $year);
	if(length $ref > 0)
	{
		getImdbData(\%details, $ref);
		if( length $episode > 0 && $episode > 0 )
		{
			if( length $season == 0)
			{
				# if no season info, then assume it is season 1
				$season = 1;
			}
			if (getImdbEpisodeData(\%details, $ref, $season, $episode) )
			{
				$details{Season} = $season;
				$details{Episode} = $episode;
			}
		}
	}
	return %details;
}

sub search_by_ref
{
	my $self = shift;
	my $ref = shift;
	my %details;	
	if(length $ref > 0)
	{
		getImdbData(\%details, $ref);
		if( (length $season > 0 && $season > 0) && (length $episode > 0 && $episode > 0) )
		{
			getImdbEpisodeData(\%details, $ref);
		}
	}
	return %details;
}

sub new
{
    my($class, %args) = @_;
    my $self = bless({}, $class);
    
    return $self;
}

sub getImdbEpisodeData
{
	my $file_details = shift;
	my $imdb_ref = shift;
	my $season = shift;
	my $episode = shift;

	my $ua = new LWP::UserAgent(agent => 'Firefox');
	my $episodeHTML = $ua->request(HTTP::Request->new(GET => "http://www.imdb.com/title/tt".$imdb_ref."/episodes"));
	my $episodeParser = new HTML::TreeBuilder;
	my $episodeDoc = $episodeParser->parse($episodeHTML->content);
	$episodeDoc->objectify_text();

	if( length $season == 0 )
	{
		$season = 1;
	}

	my $episode_heading = "Season $season, Episode $episode: ";
	print "Looking for: ".$episode_heading."\n";
	my $episodeData = $episodeDoc->look_down("_tag","~text","text",$episode_heading);
	if($episodeData)
	{
		#Get the episode name
		$name_node = $episodeData->right();
		$name_node = $name_node->look_down("_tag","~text");
		$file_details->{Subtitle} = $name_node->attr_get_i('text');
	
		#get the year
		$name_node = $episodeData->parent()->right();
		$name_node = $name_node->look_down("_tag","strong");
		if($name_node)
		{
			$name_node = $name_node->look_down("_tag","~text");
			if($name_node)
			{
				$year = $name_node->attr_get_i('text');
				($file_details->{Year}) = $year =~ m/\S+\s\S+\s(\S+)/;
			}
		}
	
		#get the plot
		$name_node = $episodeData->parent()->right()->right()->right();
		$name_node = $name_node->look_down("_tag","~text");
		if($name_node)
		{
			$file_details->{Plot} = $name_node->attr_get_i('text');
		}
		$result=1;
	}
	else
	{
		$result=0;
	}
	return $result;
}


sub trim {
   my ($str) = @_;
   $str =~ s/^\s+//;
   $str =~ s/\s+$//;
   return $str;
}

# returns text within 'data' between 'beg' and 'end' matching strings
sub parseBetween {
   my ($data, $beg, $end)=@_; # grab parameters

   my $ldata = lc($data);
   my $start = index($ldata, lc($beg)) + length($beg);
   my $finish = index($ldata, lc($end), $start);
   if ($start != (length($beg) -1) && $finish != -1) {
      my $result = substr($data, $start, $finish - $start);
      # return w/ decoded numeric character references
      # (see http://www.w3.org/TR/html4/charset.html#h-5.3.1)
      decode_entities($result);
      return $result;
   }
   return "";
}

# get Movie Data
sub getImdbData {
   my ($file_details, $movieid)=@_; # grab movieid parameter
   if (defined $opt_d) { printf("# looking for movie id: '%s'\n", $movieid);}

   my $name_link_pat = qr'itemprop="name">([^<]*)</span>'m;

   # get the search results  page
   my $request = "http://www.imdb.com/title/tt" . $movieid . "/";
   if (defined $opt_d) { printf("# request: '%s'\n", $request); }
   my $response = get $request;
   if (defined $opt_r) { printf("%s", $response); }

   # parse title and year
   my $year = "";
   my $title = parseBetween($response, "<title>", "</title>");
   if ($title =~ m#(.+) \(.*?(\d{4}).*\)#) # Note some years have a /II after them?
   {
      $title = $1;
      ($year) = $2 =~ m/(\d\d\d\d)/;
   }
   elsif ($title =~ m#(.+) \(\?\?\?\?\)#)
   {
      $title = $1;
   }
	$title =~ s/\"//g;

   # parse director
   my $data = parseBetween($response, "Director:", "</div>");
   if (!length($data)) {
      $data = parseBetween($response, ">Directors:</h4>", "</div>");
   }
   my $director = join(",", ($data =~ m/$name_link_pat/g));

   # parse writer
   # (Note: this takes the 'first' writer, may want to include others)
   $data = parseBetween($response, ">Writers <a href=\"/wga\">(WGA)</a>:</h5>", "</div>");
   if (!length($data)) {
         $data = parseBetween($response, ">Writer:</h5>", "</div>");
   }
   if (!length($data)) {
         $data = parseBetween($response, ">Writers:</h5>", "</div>");
   }
   my $writer = join(",", ($data =~ m/$name_link_pat/g));

   # parse release date
   my $releasedate = parseBetween($response, ">Release Date:</h5>", "<a ");
   if( $releasedate )
   {
      $releasedate =~ s/[\r\n]//g;
      $releasedate =~ s/.*?(\d\d\d\d).*/\1/;
   }

   # parse plot
   my $plot = parseBetween($response, ">Storyline</h2>", "<em");
   if (!$plot) {
      $plot = parseBetween($response, ">Plot Summary:</h5>", "</div>");
   }
   if (!$plot) {
      $plot = parseBetween($response, ">Plot:</h5>", "</div>");
   }

   if ($plot) {
	# remove any <div> markers
	$plot =~ s/<div [^>]*>//g;
	$plot =~ s/<p>//g;
	$plot =~ s/&#x27;/\'/g;
      # replace name links in plot (example 0388795)
      $plot =~ s/$name_link_pat/$1/g;

      # replace title links
      my $title_link_pat = qr!<a href="/title/[^"]*">([^<]*)</a>!m;
      $plot =~ s/$title_link_pat/$1/g;

      # plot ends at first remaining link
      my $plot_end = index($plot, "<a ");
      if ($plot_end != -1) {
         $plot = substr($plot, 0, $plot_end);
      }
      $plot = trim($plot);
   }

   # parse user rating
   my $userrating = parseBetween($response, ">User Rating:</b>", "</b>");
   $userrating = parseBetween($userrating, "<b>", "/");

   # parse MPAA rating
   my $ratingcountry = "USA";
   my $movierating = trim(parseBetween($response, ';m=', ';mh='));
#   if ($movierating) {
#       ($movierating) = $movierating =~ m/Rated ([^ ]*) .*/;
#   }

   # parse movie length
   my $rawruntime = trim(parseBetween($response, ">Runtime:</h4>", "</div>"));
   my $runtime = trim(parseBetween($rawruntime, '">', " min"));
   for my $country (@countries) {
      last if ($runtime =~ /^-?\d/);
      $runtime = trim(parseBetween($rawruntime, "$country:", " min"));
   }

   # parse cast
   #  Note: full cast would be from url:
   #    www.imdb.com/title/<movieid>/fullcredits

   my $cast = "";
   $data = parseBetween($response, "<h2>Cast</h2>","</table>");
   if ($data) {
      $cast = join(',', ($data =~ m/$name_link_pat/g));
      $cast = trim($cast);
   }


   # parse genres
   my $lgenres = "";
   $data = parseBetween($response, "<h5>Genre:</h5>","</div>");
   if ($data) {
      my $genre_pat = qr'/Sections/Genres/(?:[a-z ]+/)*">([^<]+)<'im;
      $lgenres = join(',', ($data =~ /$genre_pat/g));
   }

   # parse countries
   $data = parseBetween($response, "Country:</h5>","</div>");
   my $country_pat = qr'/Sections/Countries/[A-Z]+/">([^<]+)</a>'i;
   my $lcountries = trim(join(",", ($data =~ m/$country_pat/g)));


   # get poster
   my $uri = "";
       $data = parseBetween($response, "id=\"img_primary\">", "<\/td>");
       if ($data ne "") {
           $uri = parseBetween($data, "src=\"", "\"");
           $uri =~ s/SX\d+_/SX400_/;
	}
   $file_details->{PosterUrl} = $uri;


   # output fields (these field names must match what MythVideo is looking for)
	$file_details->{Title} = $title;
	$file_details->{Director} = $director;
	$file_details->{Plot} = $plot;
	$file_details->{Year} = $year;
	$file_details->{Cast} = $cast;
	$file_details->{Length} = $runtime;
	$file_details->{Rating} = $movierating;
#   print "Title:$title\n";
#   print "Year:$year\n";
#   print "ReleaseDate:$releasedate\n";
#   print "Director:$director\n";
#   print "Plot:$plot\n";
#   print "UserRating:$userrating\n";
#   print "MovieRating:$movierating\n";
#   print "Runtime:$runtime\n";
#   print "Writers: $writer\n";
#   print "Cast:$cast\n";
#   print "Genres: $lgenres\n";
#   print "Countries: $lcountries\n";
}

# dump Movie Poster
sub getMoviePoster {
   my ($movieid)=@_; # grab movieid parameter
   if (defined $opt_d) { printf("# looking for movie id: '%s'\n", $movieid);}

   # get the search results  page
   my $request = "http://www.imdb.com/title/tt" . $movieid . "/posters";
   if (defined $opt_d) { printf("# request: '%s'\n", $request); }
   my $response = get $request;
   if (defined $opt_r) { printf("%s", $response); }

   if (!defined $response) {return;}

   my $uri = "";

   # look for references to impawards.com posters - they are high quality
   my $site = "http://www.impawards.com";
   my $impsite = parseBetween($response, "<a href=\"".$site, "\">".$site);

   # jersey girl fix
   $impsite = parseBetween($response, "<a href=\"http://impawards.com","\">http://impawards.com") if ($impsite eq "");

   if ($impsite) {
      $impsite = $site . $impsite;

      if (defined $opt_d) { print "# Searching for poster at: ".$impsite."\n"; }
      my $impres = get $impsite;
      if (defined $opt_d) { printf("# got %i bytes\n", length($impres)); }
      if (defined $opt_r) { printf("%s", $impres); }

      # making sure it isnt redirect
      $uri = parseBetween($impres, "0;URL=..", "\">");
      if ($uri ne "") {
         if (defined $opt_d) { printf("# processing redirect to %s\n",$uri); }
         # this was redirect
         $impsite = $site . $uri;
         $impres = get $impsite;
      }

      # do stuff normally
      $uri = parseBetween($impres, "<img SRC=\"posters/", "\" ALT");
      # uri here is relative... patch it up to make a valid uri
      if ($uri =~ /\.(jpe?g|gif|png)$/) {
          if (!($uri =~ /http:(.*)/ )) {
             my $path = substr($impsite, 0, rindex($impsite, '/') + 1);
             $uri = $path."posters/".$uri;
          }
          if (defined $opt_d) { print "# found ipmawards poster: $uri\n"; }
      }
      else {
          $uri = "";
      }
   }

   # try looking on nexbase
   if ($uri eq "" && $response =~ m/<a href="([^"]*)">([^"]*?)nexbase/i) {
      if ($1 ne "") {
         if (defined $opt_d) { print "# found nexbase poster page: $1 \n"; }
         my $cinres = get $1;
         if (defined $cinres) {
            if (defined $opt_d) { printf("# got %i bytes\n", length($cinres)); }
            if (defined $opt_r) { printf("%s", $cinres); }

            if ($cinres =~ m/<a id="photo_url" href="([^"]*?)" ><\/a>/i) {
               if (defined $opt_d) { print "# nexbase url retreived\n"; }
               $uri = $1;
            }
         }
      }
   }

   # try looking on cinemablend
   if ($uri eq "" && $response =~ m/<a href="([^"]*)">([^"]*?)cinemablend/i) {
      if ($1 ne "") {
         if (defined $opt_d) { print "# found cinemablend poster page: $1 \n"; }
         my $cinres = get $1;
         if (defined $opt_d) { printf("# got %i bytes\n", length($cinres)); }
         if (defined $opt_r) { printf("%s", $cinres); }
        if ($cinres =~ m#<img\b[^>]+\bsrc="(/images/reviews/[^"]*?)"#i) {
            if (defined $opt_d) { print "# cinemablend url retreived\n"; }
            $uri = "http://www.cinemablend.com/".$1;
         }
      }
   }

   # if the impawards site attempt didn't give a filename grab it from imdb
   if ($uri eq "") {
       if (defined $opt_d) { print "# looking for imdb posters\n"; }
       my $host = "http://posters.imdb.com/posters/";

       $uri = parseBetween($response, $host, "\"><td><td><a href=\"");
       if ($uri ne "") {
           $uri = $host.$uri;
       } else {
          if (defined $opt_d) { print "# no poster found\n"; }
       }
   }



   my @movie_titles;
   my $found_low_res = 0;
   my $k = 0;

   # no poster found, take lowres image from imdb
   if ($uri eq "") {
      if (defined $opt_d) { print "# looking for lowres imdb posters\n"; }
      my $host = "http://www.imdb.com/title/tt" . $movieid . "/";
      $response = get $host;

      # Better handling for low resolution posters
      #
      if ($response =~ m/<a name="poster".*<img.*src="([^"]*).*<\/a>/ig) {
         if (defined $opt_d) { print "# found low res poster at: $1\n"; }
         $uri = $1;
         $found_low_res = 1;
      } else {
         if (defined $opt_d) { print "# no low res poster found\n"; }
         $uri = "";
      }

      if (defined $opt_d) { print "# starting to look for movie title\n"; }

      # get main title
      if (defined $opt_d) { print "# Getting possible movie titles:\n"; }
      $movie_titles[$k++] = parseBetween($response, "<title>", "<\/title>");
      if (defined $opt_d) { print "# Title: ".$movie_titles[$k-1]."\n"; }

      # now we get all other possible movie titles and store them in the titles array
      while($response =~ m/>([^>^\(]*)([ ]{0,1}\([^\)]*\)[^\(^\)]*[ ]{0,1}){0,1}\(informal title\)/g) {
         $movie_titles[$k++] = trim($1);
         if (defined $opt_d) { print "# Title: ".$movie_titles[$k-1]."\n"; }
      }

   }

   print "$uri\n";
}

# dump Movie list:  1 entry per line, each line as 'movieid:Movie Title'
sub searchImdb {
   my ($search_title, $options, $search_year)=@_; # grab parameters

	# if there are more than 4 errors. then maybe we don't want it
	my $best_score = 5;
	my $best_ref = "";
	my $errors;

   # If we wanted to inspect the file for any reason we can do that now

   #
   # Convert filename into a query string
   # (use same rules that Metadata::guesTitle does)
   my $query = $search_title;
   $query = uri_unescape($query);  # in case it was escaped

   if (!$options) { $options = "" ;}
   if (defined $opt_d) {
      printf("# query: '%s', options: '%s'\n", $query, $options);
   }

   # get the search results  page
   #    some known IMDB options are:
   #         type=[fuzy]         looser search
   #         from_year=[int]     limit matches to year (broken at imdb)
   #         to_year=[int]       limit matches to year (broken at imdb)
   #         sort=[smart]        ??
   #         tv=[no|both|only]   limits between tv and movies (broken at imdb)
   #$options = "tt=on;nm=on;mx=20";  # not exactly clear what these options do
   my $request = "http://www.imdb.com/find?q=$query;$options";
   if (defined $opt_d) { printf("# request: '%s'\n", $request); }
   my $response = get $request;
   if (defined $opt_r) {
      print $response;
      exit(0);
   }

   # check to see if we got a results page or a movie page
   my $movienum = parseBetween($response, "add_to_watchlist\" data-const=\"tt", "\"");
   if (!$movienum) {
      $movienum = parseBetween($response, "href=\"/title/tt", "/ref_");
   }
   if ($movienum) {
      if ($movienum !~ m/^[0-9]+$/) {
         if (defined $opt_d) {
            printf("# Error: IMDB movie number ($movienum), isn't.\n");
         }
         return 0;
      }

      if (defined $opt_d) { printf("# redirected to movie page\n"); }
      my $movietitle = parseBetween($response, "<title>", "</title>");
      $movietitle =~ m#(.+) \((\d+)\)#;
      $movietitle = $1;
      print "$movietitle\n";
      printf "  Found match: ".$movietitle."\n";
      return $movienum;
   }

   # extract possible matches
   #    possible matches are grouped in several catagories:
   #        exact, partial, and approximate
   my $popular_results = parseBetween($response, "</a>Titles</h3>",
                                              "</table>");
   my $exact_matches = parseBetween($response, ">More title matches</a>",
                                              "</table>");
   my $partial_matches = parseBetween($response, "<b>Titles (Partial Matches)</b>",
                                              "</table>");
#   my $approx_matches = parseBetween($response, "<b>Titles (Approx Matches)</b>",
#                                               "</table>");
   # parse movie list from matches
   my $beg = "<tr class";
   my $end = "</tr>";
   my $count = 0;
   my @movies;

#   my $data = $exact_matches.$partial_matches;
   my $data = $popular_results.$exact_matches;
   # resort to partial matches if no exact
   if ($data eq "") { $data = $partial_matches; }
   # resort to approximate matches if no exact or partial
#   if ($data eq "") { $data = $approx_matches; }
   if ($data eq "") {
      if (defined $opt_d) { printf("# no results\n"); }
      return;
   }
   my $start = index($data, $beg);
   my $finish = index($data, $end, $start);
   my $year;
   my $type;
   my $title;
   while ($start != -1 && $start < length($data)) {
      $start += length($beg);
      my $entry = substr($data, $start, $finish - $start);
      $start = index($data, $beg, $finish + 1);
      $finish = index($data, $end, $start);

      my $title = "";
      my $year = "";
      my $type = "";
      my $movienum = "";

      # Some titles are identical, IMDB indicates this by appending /I /II to
      # the release year.
      #   e.g. "Mon meilleur ami" 2006/I vs "Mon meilleur ami" 2006/II
      if ($entry =~ m/<a href="\/title\/tt(\d+)\/.*\" >(.+)<\/a> \(?[a-z]*\)? *\((\d+)\) *(\((.+)\))?/i) {
          $movienum = $1;
          $title = $2;
          $year = $3;
          $type = $4 if ($4);
           if (defined $opt_d) {
               printf("Parsing entry: $movienum, $title, $year, $type\n");
           }
      } else {
           if (defined $opt_d) {
               printf("Unrecognized entry format ($entry)\n");
           }
           next;
      }

      my $skip = 0;

      # fix broken 'tv=no' option
      if ($options =~ /tv=no/) {
         if ($type eq "TV") {
            if (defined $opt_d) {printf("# skipping TV program: %s\n", $title);}
            $skip = 1;
         }
      }
      if ($options =~ /tv=only/) {
         if ($type eq "") {
            if (defined $opt_d) {printf("# skipping Movie: %s\n", $title);}
            $skip = 1;
         }
      }
      # fix broken 'from_year=' option
      if ($options =~ /from_year=(\d+)/) {
         if ($year < $1) {
            if (defined $opt_d) {printf("# skipping b/c of yr: %s\n", $title);}
            $skip = 1;
         }
      }
      # fix broken 'to_year=' option
      if ($options =~ /to_year=(\d+)/) {
         if ($year > $1) {
            if (defined $opt_d) {printf("# skipping b/c of yr: %s\n", $title);}
            $skip = 1;
         }
      }

      # option to strip out videos (I think that's what '(V)' means anyway?)
      if ($options =~ /video=no/) {
         if ($type eq "V") {
            if (defined $opt_d) {
                printf("# skipping Video program: %s\n", $title);
            }
            $skip = 1;
         }
      }

      # (always) strip out video game's (why does IMDB give these anyway?)
      if ($type eq "VG") {
         if (defined $opt_d) {printf("# skipping videogame: %s\n", $title);}
         $skip = 1;
      }

      # add to array
      if (!$skip) {
		$title =~ s/\"//g;
		$title =~ s/-/ /g;
		$errors = scoreMatch($search_title, $title);
		printf "$title $year\n";
		if($errors < $best_score)
		{
			if($year == $search_year)
			{
				$best_score = $errors - 1;
			}
			else
			{
				$best_score = $errors;
			}
			$best_ref = $movienum;
			printf "  Found better match: ".$title."\n";
		}
		elsif($errors == $best_score)
		{
			if($year == $search_year)
			{
				$best_score = $errors - 1;
				$best_ref = $movienum;
				print "  Found Better Match: $title ($year)\r\n";
			}
		}
      }
   }
   return $best_ref;
}
sub scoreMatch
{
	my $first = shift;
	my $second = shift;
	$first =~ s/[^\w\s]//g;
	$second =~ s/[^\w\s]//g;
	my $errors = distance(lc($first), lc($second));
#	print " Errors between \"$first\" and \"$second\": $errors\r\n";
	return $errors;
}

# vim: set expandtab ts=3 sw=3 :
1;
